package com.matrix.gateway.filter;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.matrix.common.constant.HeaderConstants;
import com.matrix.common.domain.Result;
import com.matrix.common.enums.ErrorCode;
import com.matrix.gateway.config.WhiteListProperties;
import com.matrix.gateway.utils.JwtUtil;
import io.jsonwebtoken.Claims;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.MDC;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.UUID;

/**
 * @author 有点甜
 * @since 2025/7/25
 */
@Component
@RequiredArgsConstructor
@Slf4j
public class AuthGlobalFilter implements GlobalFilter, Ordered {

    private final WhiteListProperties whiteListProperties;
    private final JwtUtil jwtUtil;
    private final ObjectMapper objectMapper;
    private final AntPathMatcher pathMatcher = new AntPathMatcher();

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        return Mono.deferContextual(ctx -> {
            String traceId = exchange.getRequest().getHeaders().getFirst(HeaderConstants.TRACE_ID);
            if (traceId == null) {
                traceId = UUID.randomUUID().toString().replace("-", "");
            }
            MDC.put("traceId", traceId);

            ServerHttpRequest request = exchange.getRequest();
            String path = request.getURI().getPath();

            for (String whitePath : whiteListProperties.getWhiteList()) {
                if (pathMatcher.match(whitePath, path)) {
                    log.info("Path {} is in white list, skipping auth.", path);
                    ServerHttpRequest modifiedRequest = request.mutate()
                            .header(HeaderConstants.TRACE_ID, traceId)
                            .build();
                    return chain.filter(exchange.mutate().request(modifiedRequest).build());
                }
            }

            String token = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
            if (token == null || !token.startsWith("Bearer ")) {
                return unauthorizedResponse(exchange, "Request is missing a token");
            }
            token = token.substring(7);

            Claims claims;
            try {
                if (jwtUtil.isTokenExpired(token)) {
                    return unauthorizedResponse(exchange, "Token has expired");
                }
                claims = jwtUtil.getClaimsFromToken(token);
            } catch (Exception e) {
                log.error("Token parsing failed: {}", e.getMessage());
                return unauthorizedResponse(exchange, "Token is invalid");
            }

            Long userId = claims.get("uid", Long.class);
            String userRole = claims.get("rol", String.class);

            ServerHttpRequest modifiedRequest = request.mutate()
                    .header(HeaderConstants.TRACE_ID, traceId)
                    .header(HeaderConstants.USER_ID, String.valueOf(userId))
                    .header(HeaderConstants.USER_ROLE, userRole)
                    .build();

            return chain.filter(exchange.mutate().request(modifiedRequest).build());
        }).doFinally(signalType -> MDC.clear());
    }

    @Override
    public int getOrder() {
        return -100;
    }

    private Mono<Void> unauthorizedResponse(ServerWebExchange exchange, String message) {
        ServerHttpResponse response = exchange.getResponse();
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
        Result<Object> result = Result.error(ErrorCode.UNAUTHORIZED);
        result.setMessage(message);

        byte[] bits;
        try {
            bits = objectMapper.writeValueAsBytes(result);
        } catch (JsonProcessingException e) {
            bits = "{\"code\":401,\"message\":\"Unauthorized\"}".getBytes(StandardCharsets.UTF_8);
        }
        DataBuffer buffer = response.bufferFactory().wrap(bits);
        return response.writeWith(Mono.just(buffer));
    }
}
